#include "MapForm.h"
#include <cmath>
#include <FSystem.h>
#include "BadaImage.h"
#include "OpennavUtils.h"

using namespace Osp::App;
using namespace Osp::Ui;
using namespace Osp::Ui::Controls;
using namespace Osp::Graphics;
using namespace Osp::Base::Collection;
using namespace Osp::Base;
using namespace Osp::Locations;
using namespace std;
using namespace opennav;

MapForm::MapForm() :
__clientRect(), __pLastTouchInfoList(null), __touchFlags(0),
__lastDoubleTapTick(0), lastZoomTouch1(0,0), lastZoomTouch2(0,0),
__isLocationUpdated(false), __isRequestLastLocation(false),
__isValidLocation(false), __interval(0), __latitude(0.0),
__longitude(0.0), __altitude(0.0f),
__speed(0.0), __course(0.0), __errorCode(0),
__pLocationProvider(null), __pSatelliteInfo(null), __pMapProvider(null),
		__pReverseGeocodingProvider(null),
__pCurrentSection(null), __pCurrentSectionLock(null), __pan(0, 0),
__scale(0.0f), __touch(false), __follow(true), __azimuth(0.0f), hack(1.0f),
__pCurrentRoad(null), __pCurrentRoadSegment(null), __pCurrentResolvedCoordinate(null),
__pCurrentRoadLock(null) { }

MapForm::~MapForm() {

}

result MapForm::Construct(OpennavMap* map) {
	Form::Construct(L"IDF_MAP");
	__pMap = map;
	return E_SUCCESS;
}

result MapForm::OnInitializing() {
	result r = E_SUCCESS;
	__pLabelStreet = static_cast<Label *>(GetControl(L"IDC_LABEL_STREET"));
	if (__pLabelStreet != null) {
		__pLabelStreet->SetTextHorizontalAlignment(ALIGNMENT_CENTER);
		__pLabelStreet->SetTextVerticalAlignment(ALIGNMENT_MIDDLE);
	}
	AddOrientationEventListener(*this);
    __pOptionMenu = new OptionMenu();
    __pOptionMenu->Construct();
    __pOptionMenu->SetShowState(false);
    __pOptionMenu->AddItem("Position verfolgen", ID_OPTION_CENTER_GPS);
    __pOptionMenu->AddItem("Nach Norden ausrichten", ID_OPTION_NORTH);
    __pOptionMenu->AddItem("Hack", ID_OPTION_HACK);
    __pOptionMenu->AddActionEventListener(*this);
    this->AddControl(*__pOptionMenu);
    this->SetOptionkeyActionId(ID_ON_OPTION_MENU);
    this->AddOptionkeyActionListener(*this);
	AddTouchEventListener(*this);
	Touch touch;
	touch.SetMultipointEnabled(*this, true);
	__clientRect = GetClientAreaBounds();
	if (__pLocationProvider == null) {
		__pLocationProvider = new LocationProvider();
		r = __pLocationProvider->Construct(LOC_METHOD_GPS);
		if (IsFailed(r)) {
			delete __pLocationProvider;
			__pLocationProvider = null;
			AppLogException("[%s] Construct is failed.\n", GetErrorMessage(r));
			return r;
		}
		r = __pLocationProvider->RequestLocationUpdates(*this, 1, true);
		if (IsFailed(r)) {
			AppLogException("[%s] Construct is failed.\n", GetErrorMessage(r));
		}
	}
	__pCurrentSectionLock = new Mutex();
	__pCurrentSectionLock->Create();
	__pMapProvider = new OpennavMapServiceProvider();
	r = __pMapProvider->Construct(__pMap);
	if (IsFailed(r)) {
		delete __pMapProvider;
		__pMapProvider = null;
		delete __pMap;
		__pMap = null;
	}
	__pCurrentRoadLock = new Mutex();
	__pCurrentRoadLock->Create();
	__pMapProvider->AddOpennavMapProviderListener(*this);
	__pReverseGeocodingProvider = new OpennavReverseGeocodingServiceProvider();
	r = __pReverseGeocodingProvider->Construct(__pMap);
	if (IsFailed(r)) {
		delete __pReverseGeocodingProvider;
		__pReverseGeocodingProvider = null;
	}
	Coordinates coordinate;
	coordinate.Set(51.33, 12.35, 0.0);
	__pMapProvider->RequestMap(coordinate, 256.0f, 0.0f, __clientRect.width, __clientRect.height);
	return r;
}

result MapForm::OnTerminating() {
	result r = E_SUCCESS;
	return r;
}

result MapForm::OnDraw(void) {
	Canvas* pCanvas = GetCanvasN(__clientRect);
	if (pCanvas == null) return E_FAILURE;
	__pCurrentSectionLock->Acquire();
	if (__pCurrentSection != null) {
		__pCurrentSection->Draw(*pCanvas, __pan, __scale);
		if (__isLocationUpdated && __touch == false) {
			Coordinates coordinate;
			coordinate.Set(__latitude, __longitude, __altitude);
			Osp::Graphics::Point pos;
			__pCurrentSection->TransformCoordinatesToPixels(coordinate, pos);
			pos.x += __pan.x;
			pos.y += __pan.y;
			if (pos.x >= __clientRect.x && pos.x < __clientRect.x + __clientRect.width &&
				pos.y >= __clientRect.y && pos.y < __clientRect.y + __clientRect.height) {
				Rectangle c(pos.x - 15, pos.y - 15, 30, 30);
				pCanvas->SetForegroundColor(Osp::Graphics::Color(0x006400, false));
				pCanvas->FillEllipse(Osp::Graphics::Color(0x32cd32, false), c);
				pCanvas->SetLineWidth(5);
				pCanvas->DrawEllipse(c);
			}
		}
		__pCurrentRoadLock->Acquire();
		if (__pCurrentResolvedCoordinate != null) {
			Osp::Graphics::Point pos;
			__pCurrentSection->TransformCoordinatesToPixels(*__pCurrentResolvedCoordinate, pos);
			Rectangle c(pos.x - 4, pos.y - 4, 8, 8);
			pCanvas->SetForegroundColor(Osp::Graphics::Color(0x640000, false));
			pCanvas->FillEllipse(Osp::Graphics::Color(0xcd3232, false), c);
			pCanvas->SetLineWidth(2);
			pCanvas->DrawEllipse(c);
		}
		__pCurrentRoadLock->Release();
	}
	__pCurrentSectionLock->Release();
	delete pCanvas;
	return E_SUCCESS;
}

void MapForm::OnActionPerformed(const Osp::Ui::Control& source, int actionId) {
	switch(actionId) {
	case ID_ON_OPTION_MENU:
		__pOptionMenu->SetShowState(true);
		__pOptionMenu->Show();
		break;
	case ID_OPTION_CENTER_GPS:
		__follow = true;
		if (__isValidLocation) {
			RequestRedraw(true);
		}
		break;
	case ID_OPTION_NORTH:
		__azimuth = 0.0f;
		UpdateMap();
		break;
	case ID_OPTION_HACK:
		hack = -hack;
		break;
	}
}

void MapForm::OnTouchDoublePressed(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {

}

void MapForm::OnTouchFocusIn(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {

}

void MapForm::OnTouchFocusOut(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {

}

void MapForm::OnTouchLongPressed(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {

}

void MapForm::OnTouchMoved(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {
	ChangeMap(source);
	__follow = false;
}

void MapForm::OnTouchPressed(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {
	__touch = true;
	ChangeMap(source);
}

void MapForm::OnTouchReleased(const Osp::Ui::Control &source, const Osp::Graphics::Point &currentPosition, const Osp::Ui::TouchEventInfo &touchInfo) {
	__touch = false;
	ChangeMap(source);
	RequestRedraw(true);
	UpdateMap();
}

void MapForm::ChangeMap(const Osp::Ui::Control& source)
{
	Touch touch;
	IList* pTouchInfoList = touch.GetTouchInfoListN(source);
	int lastTouchCount = 0;
	if (__pLastTouchInfoList)
	{
		lastTouchCount = __pLastTouchInfoList->GetCount();
		if (pTouchInfoList) ChangeMap(*__pLastTouchInfoList, *pTouchInfoList);

		__pLastTouchInfoList->RemoveAll(true);
		delete __pLastTouchInfoList;
		__pLastTouchInfoList = null;
	}
	else __touchFlags = 0;
	if (null == pTouchInfoList)
	{
		if (!(__touchFlags & TOUCH_FLAG_MOVED) && (__touchFlags & TOUCH_FLAG_DOUBLE_TAP))
		{
			long long curTick;
			Osp::System::SystemTime::GetTicks(curTick);
			if (int(curTick - __lastDoubleTapTick) < 500)
			{
				__scale = __scale * 2.0f;
				RequestRedraw(true);
			}
		}
		__touchFlags = 0;
	}

	__pLastTouchInfoList = pTouchInfoList;
}

void MapForm::ChangeMap(const Osp::Base::Collection::IList& oldTouchInfo, const Osp::Base::Collection::IList& newTouchInfo)
{
	if (2 == oldTouchInfo.GetCount())
	{
		__touchFlags |= TOUCH_FLAG_DOUBLE_TAP;
		Osp::System::SystemTime::GetTicks(__lastDoubleTapTick);
	}
	if (1 == oldTouchInfo.GetCount())
	{
		const TouchInfo* pNewTouchInfo = null;
		const TouchInfo* pOldTouchInfo = static_cast<const TouchInfo*>(oldTouchInfo.GetAt(0));
		for (int i = 0; i < newTouchInfo.GetCount(); ++i)
		{
			pNewTouchInfo = static_cast<const TouchInfo*>(newTouchInfo.GetAt(i));
			if (pOldTouchInfo->id == pNewTouchInfo->id)
			{
				Osp::Graphics::Point move(pNewTouchInfo->position - pOldTouchInfo->position);
				if (0 != move.x || 0 != move.y)
				{
					PanMap(move.x, move.y);
					RequestRedraw(true);
					__touchFlags |= TOUCH_FLAG_MOVED;
				}
				break;
			}
		}
	}
	else if (2 == oldTouchInfo.GetCount())
	{
		const TouchInfo* pOldTouchInfo1 = static_cast<const TouchInfo*>(oldTouchInfo.GetAt(0));
		const TouchInfo* pOldTouchInfo2 = static_cast<const TouchInfo*>(oldTouchInfo.GetAt(1));
		const TouchInfo* pNewTouchInfo = null;
		const TouchInfo* pNewTouchInfo1 = null;
		const TouchInfo* pNewTouchInfo2 = null;

		for (int i = 0; i < newTouchInfo.GetCount(); ++i)
		{
			pNewTouchInfo = static_cast<const TouchInfo*>(newTouchInfo.GetAt(i));
			if (pOldTouchInfo1->id == pNewTouchInfo->id)
			{
				pNewTouchInfo1 = pNewTouchInfo;
			}
			else if (pOldTouchInfo2->id == pNewTouchInfo->id)
			{
				pNewTouchInfo2 = pNewTouchInfo;
			}
		}

		if (pNewTouchInfo1 && pNewTouchInfo2)
		{
			Osp::Graphics::Point oldDistance = pOldTouchInfo1->position - pOldTouchInfo2->position;
			Osp::Graphics::Point newDistance = pNewTouchInfo1->position - pNewTouchInfo2->position;

			Osp::Graphics::Point oldCenter = pOldTouchInfo1->position + pOldTouchInfo2->position;
			oldCenter.x /= 2;
			oldCenter.y /= 2;
			Osp::Graphics::Point newCenter = pNewTouchInfo1->position + pNewTouchInfo2->position;
			newCenter.x /= 2;
			newCenter.y /= 2;

			double scale = sqrt(((double)newDistance.x * newDistance.x + newDistance.y * newDistance.y) / ((double)oldDistance.x * oldDistance.x + oldDistance.y * oldDistance.y));

			Osp::Graphics::Point screenCenter = Osp::Graphics::Point(__clientRect.width / 2, __clientRect.height / 2) + __clientRect.GetTopLeft();
			Osp::Graphics::Point before = screenCenter - (oldCenter);
			Osp::Graphics::Point after = newCenter - screenCenter;

			PanMap(before.x, before.y);
			ZoomMap(scale);
			PanMap(after.x, after.y);
			RequestRedraw(true);
			__touchFlags |= TOUCH_FLAG_MOVED;
		}
		else
		{
			Osp::Graphics::Point move(0, 0);
			if (pNewTouchInfo1)
			{
				move = pNewTouchInfo1->position - pOldTouchInfo1->position;
			}
			else if (pNewTouchInfo2)
			{
				move = pNewTouchInfo2->position - pOldTouchInfo2->position;
			}

			if (0 != move.x || 0 != move.y)
			{
				PanMap(move.x, move.y);
				RequestRedraw(true);
				__touchFlags |= TOUCH_FLAG_MOVED;
			}
		}
	}
}

void MapForm::OnOrientationChanged(const Osp::Ui::Control &source, Osp::Ui::OrientationStatus orientationStatus) {
	__clientRect = GetClientAreaBounds();
	Draw();
	UpdateMap();
}

void MapForm::OnLocationUpdated(Location& location)
{
//	long long int dt = 0;
//	if (__isLocationUpdated) {
//		dt = location.GetTimestamp() - __timestamp;
//	}
	__isLocationUpdated = true;
	const QualifiedCoordinates* pQualifiedCoord = location.GetQualifiedCoordinates();
	__speed = location.GetSpeed();
	__course = location.GetCourse();
	__timestamp = location.GetTimestamp();
	if(pQualifiedCoord){
		__isValidLocation = true;
		__latitude = pQualifiedCoord->GetLatitude();
		__longitude = pQualifiedCoord->GetLongitude();
		__altitude = pQualifiedCoord->GetAltitude();
		__errorCode = 0;
		if (__follow) {
			Coordinates coordinate;
			coordinate.Set(__latitude, __longitude, __altitude);
			float zoom = max(min((__speed - 20.0f) * 2.0f, 256.0f), 32.0f);
			__azimuth = __course;
			__pMapProvider->RequestMap(coordinate, zoom, hack * __course, __clientRect.width, __clientRect.height);
		}
		UpdateRoad();
	}else{
		__isValidLocation = false;
		__errorCode = location.GetErrorCode();
	}
	const SatelliteInfo* pInfo = location.GetSatelliteInfo();
	if(__pSatelliteInfo)
		delete __pSatelliteInfo;
	if(pInfo) {
		__pSatelliteInfo = new SatelliteInfo(*pInfo);
	} else {
		__pSatelliteInfo = null;
	}
	RequestRedraw(true);
}

void MapForm::OnProviderStateChanged(LocProviderState newState)
{

}

void MapForm::OnMapRequestFinished(OpennavMapSection* section, int requestId) {
	AppLogDebug("map request %d finished", requestId);
	if (__touch == false) {
		__pCurrentSectionLock->Acquire();
		if (__pCurrentSection != null) {
			delete __pCurrentSection;
		}
		__pCurrentSection = section;
		__scale = 1.0f;
		__pan = Osp::Graphics::Point(0, 0);
		__pCurrentSectionLock->Release();
	}
	RequestRedraw(true);
}

void MapForm::PanMap(int dx, int dy) {
	__pan.Translate(dx / __scale, dy / __scale);
	RequestRedraw(true);
}

void MapForm::ZoomMap(float factor) {
	__scale = __scale * factor;
	RequestRedraw(true);
}

void MapForm::UpdateMap() {
	if (__touch == false) {
		__pCurrentSectionLock->Acquire();
		if (__pCurrentSection != null) {
			Coordinates mapCenter;
			float zoomLevel;
			__pCurrentSection->TransformCenter(__pan, __scale, mapCenter, zoomLevel);
			__pMapProvider->RequestMap(mapCenter, zoomLevel, __azimuth, __clientRect.width, __clientRect.height);
		}
		__pCurrentSectionLock->Release();
	}
}

void MapForm::UpdateRoad() {
	Coordinates coordinate;
	coordinate.Set(__latitude, __longitude, __altitude);
	__pReverseGeocodingProvider->ReverseGeocodeRoad(coordinate, *this);
}

void MapForm::OnReverseGeocodeResultReceived(opennav::Road& road, opennav::RoadSegment& roadSegment, opennav::RoadJunction& nextJunction, Osp::Locations::Coordinates& coordinate) {
	AppLogDebug("reverse geocode result");
	__pCurrentRoadLock->Acquire();
	if (__pCurrentRoad != null) {
		delete __pCurrentRoad;
	}
	__pCurrentRoad = new opennav::Road(road);
	if (__pCurrentRoad->labels.size() > 0) {
		Osp::Base::String roadName;
		OpennavUtils::Convert(__pCurrentRoad->labels.at(0), roadName);
		__pLabelStreet->SetText(roadName);
	} else {
		__pLabelStreet->SetText(L"");
	}
	if (__pCurrentRoadSegment != null) {
		delete __pCurrentRoadSegment;
	}
	__pCurrentRoadSegment = new opennav::RoadSegment(roadSegment);
	if (__pCurrentResolvedCoordinate != null) {
		delete __pCurrentResolvedCoordinate;
	}
	__pCurrentResolvedCoordinate = new Osp::Locations::Coordinates(coordinate);
	__pCurrentRoadLock->Release();
}

void MapForm::OnNoReverseGeocodeResultReceived() {
	AppLogDebug("no reverse geocode result");
	__pCurrentRoadLock->Acquire();
	if (__pCurrentRoad != null) {
		delete __pCurrentRoad;
		__pCurrentRoad = null;
	}
	__pLabelStreet->SetText(L"");
	if (__pCurrentRoadSegment != null) {
		delete __pCurrentRoadSegment;
		__pCurrentRoadSegment = null;
	}
	if (__pCurrentResolvedCoordinate != null) {
		delete __pCurrentResolvedCoordinate;
		__pCurrentResolvedCoordinate = null;
	}
	__pCurrentRoadLock->Release();
}

